package com.gitee.generator.universal;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.file.PathUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.gitee.generator.universal.common.FreeMarkerUtil;
import com.gitee.generator.universal.common.Constants;
import com.gitee.generator.universal.common.MethodUtil;
import com.gitee.generator.universal.common.jsonconfig.JsonConfig;
import com.gitee.generator.universal.domain.FileNameInfo;
import com.gitee.generator.universal.domain.GeneratorConfig;
import com.gitee.generator.universal.domain.Project;
import com.gitee.generator.universal.datasource.service.TableService;
import com.gitee.generator.universal.datasource.service.impl.TableServiceImpl;
import com.gitee.generator.universal.domain.Table;
import lombok.extern.slf4j.Slf4j;
import cn.hutool.core.io.FileUtil;
import freemarker.template.TemplateException;

/**
 * CodeGenerator
 * @date 2020-08-19 11:24
 * @author wsx
 */
@Slf4j
public final class CodeGenerator {
    /** 构造器*/
    private CodeGenerator() {}
    /**
     * 生成代码
     */
    public static void generate() throws IOException {
        log.info("<<<<<<<<<<<< 系统启动 >>>>>>>>>>>>>>");
        GeneratorConfig generatorConfig = JsonConfig.getObj(GeneratorConfig.class, Constants.GENERATOR_CONFIG);
        List<Map<String,Object>> projectsMap = JsonConfig.getObj(new TypeReference<List<Map<String,Object>>>(){}.getType(),Constants.PROJECTS);
        CodeGenerator.trueExit(CollUtil.isEmpty(projectsMap),"没有要生成的项目");

        log.info("projectsMap:{}",projectsMap);
        TableService tableService = new TableServiceImpl();
        for(Map<String,Object> projectMap : projectsMap){
            Project project = BeanUtil.mapToBean(projectMap,Project.class,true,CopyOptions.create().ignoreNullValue());
            if(Boolean.FALSE.equals(project.getGenerate())){
                log.debug("project:{} generate=false 跳过",project.getProjectName());
                continue;
            }

            /* 查询所有表 */
            List<Table> dbTables = tableService.listTablesWithColumns(project);
            if(CollUtil.isEmpty(dbTables)){
                log.info("项目 [{}] 没有要生成的表", project.getProjectName());
                continue;
            }
            List<Map<String,Object>> tableMaps = new ArrayList<>();
            for (Table table : dbTables) {
                table.setRemovePrefix(project.getRemovePrefix());
                /* 配置文件中 tables 为空，生成所有的表，否则只生成指定的表 */
                //noinspection JdCollectionInitShouldAssignCapacity
                Map<String,Object> tableConfig = new HashMap<>(16);
                if (CollUtil.isNotEmpty(project.getTables())) {
                    tableConfig = project.getTables().get(table.getTableName());
                    if (Boolean.TRUE.equals(MapUtil.getBool(tableConfig,Constants.GENERATE,true))) {
                        BeanUtil.copyProperties(tableConfig, table, CopyOptions.create().ignoreNullValue());
                        table.initModelNames();
                    }
                }
                tableConfig.putAll(JsonConfig.parse(JsonConfig.toJsonStr(table)));
                tableMaps.add(tableConfig);
                log.info("项目 [{}] 要生成的表：{}", project.getProjectName(), table.getTableName());
            }

            Map<String,Object> generatorConfigMap = JsonConfig.getObj(new TypeReference<Map<String,Object>>(){}.getType(), Constants.GENERATOR_CONFIG);
            generatorConfigMap.putAll(projectMap);

            /* project相关路径设置 */
            CodeGenerator.generatePackage(generatorConfig, project);
            Map<String, Object> projectCfgMap = BeanUtil.beanToMap(project);
            generatorConfigMap.putAll(projectCfgMap);
            log.info("============= 开始生成项目 ============= [{}]",project.getProjectName());
            CodeGenerator.generateFiles(generatorConfigMap, project, tableMaps); // 递归模板文件生成代码
            log.info("============= 结束生成项目 ============= [{}]",project.getProjectName());
        }
        log.info("<<<<<<<<<<<< 执行完成 >>>>>>>>>>>>>>");
    }
    /**
     * 设置Project 模版路径、文件输出路径
     */
    public static void generatePackage(final GeneratorConfig generatorConfig,final Project projectCfg) {
        String templateName = projectCfg.getTemplateName();
        String projectName = projectCfg.getProjectName(); // 项目名称
        String basePackage = projectCfg.getBasePackage(); // 包名
        CodeGenerator.trueExit(CharSequenceUtil.hasBlank(templateName,projectName,basePackage),"templateName(模板名称),projectName(projectName),包名(basePackage)都不能为空");

        String templateDir = FileUtil.normalize(generatorConfig.getTemplateBaseDir() + templateName + File.separator); // 选中的模板目录
        CodeGenerator.trueExit(!FileUtil.exist(templateDir),"!!! 所选模板不存在：" + templateDir + "，请检查配置属性: templateName !!!");
        projectCfg.setTemplateDir(templateDir);

        String outputDir = FileUtil.normalize(generatorConfig.getOutputBaseDir() + projectName + File.separator); // 输出文件路径
        projectCfg.setOutputDir(outputDir);
    }
    /**
     * 递归模板文件夹，生成代码
     */
    private static void generateFiles(final Map<String, Object> map, final Project projectCfg, final List<Map<String,Object>> tableMaps) throws IOException {
        log.info("=============   开始生成项目代码 =============");
        log.info("*** 模板目录：" + projectCfg.getTemplateDir() + " ***");
        log.info("*** 输出目录：" + projectCfg.getOutputDir() + " ***");
        FreeMarkerUtil freeMarkerUtil = FreeMarkerUtil.getInstance(Constants.FREE_MARKER_VERSION, projectCfg.getTemplateDir());
        MethodUtil m = new MethodUtil();
        map.put(Constants.M, m);
        String basePackageDir = m.toDir(projectCfg.getBasePackage());

        FileNameInfo fileNameInfo = new FileNameInfo();
        Map<String,Map<String,String>> templateFilePathMap = new HashMap<>();
        PathUtil.walkFiles(Paths.get(projectCfg.getTemplateDir()), 50, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) {
                String templateFilePath = FileUtil.normalize(file.toString()); // 模版路径
                Map<String,String> tableTargetPathMap = templateFilePathMap.computeIfAbsent(templateFilePath, k->new HashMap<>());
                for (Map<String,Object> table : tableMaps) {
                    map.put(Constants.TABLE,table);
                    try {
                        /* 文件生成路径 */
                        String targetFilePath = FileUtil.normalize(projectCfg.getOutputDir() + CharSequenceUtil.removePrefix(freeMarkerUtil.processStringTemplate(templateFilePath, map), projectCfg.getTemplateDir()));
                        CodeGenerator.trueExit(JsonConfig.Util.haveElStr(targetFilePath),String.format("模版文件未完成参数替换:%s",targetFilePath));
                        /* 文件生成路径去掉模版后缀 */
                        String filePath = targetFilePath.replace(Constants.POINT_FTL,"");
                        tableTargetPathMap.put(table.get(Constants.TABLE_NAME).toString(),filePath);
                        /* 生成父级文件夹，删除原来的文件 */
                        FileUtil.mkParentDirs(filePath);
                        FileUtil.del(filePath);

                        if(!filePath.contains(basePackageDir)){
                            continue;
                        }
                        /* 设置文件包名、全路径名 */
                        String fileMainName = FileUtil.mainName(filePath);
                        String fileExtName = Constants.POINT + FileUtil.extName(filePath);
                        String classTypeName = m.toPackage(filePath.substring(filePath.indexOf(basePackageDir),filePath.indexOf(fileExtName)));
                        String classPackage = classTypeName.substring(0,classTypeName.indexOf(fileMainName)-1);

                        fileNameInfo.getClassTypeNameMap().put(fileMainName,classTypeName);
                        fileNameInfo.getClassPackageNameMap().put(fileMainName,classPackage);
                    } catch (IOException | TemplateException e) {
                        log.error("生成出现异常",e);
                    }
                }
                return FileVisitResult.CONTINUE;
            }
        });
        map.putAll(BeanUtil.beanToMap(fileNameInfo));

        for(Map.Entry<String,Map<String,String>> templateFilePathMapEntry:templateFilePathMap.entrySet()){
            log.info("模版文件+++++> : {}", templateFilePathMapEntry.getKey());
            for (Map<String,Object> table : tableMaps) {
                map.put(Constants.TABLE,table);

                String templateFilePath = templateFilePathMapEntry.getValue().get(table.get(Constants.TABLE_NAME).toString());
                freeMarkerUtil.processTemplate(templateFilePathMapEntry.getKey(), projectCfg.getTemplateDir(), templateFilePath, map);
                log.info("生成文件-----> : {}", templateFilePath);
            }
        }
        log.info("=============   项目代码生成完成 =============");
    }

    /**
     * true 退出系统
     * @param express 表达式
     * @param errorString 错误信息
     */
    private static void trueExit(boolean express,String errorString){
        if(express){
            log.info("<<<<<<<<<<<<  系统关闭  >>>>>>>>>>>>>>:{}",errorString);
            System.exit(0); // 系统退出
        }
    }
}
